import asyncio
import random
import time
import urllib.parse
from functools import reduce
from hashlib import md5

import httpx

from config.video_zone import VIDEO_ZONES

# fmt: off
# WBI签名所需的混淆密钥编码表，用于对imgKey和subKey进行字符顺序打乱
mixinKeyEncTab = [
    46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
    33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
    61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
    36, 20, 34, 44, 52
]
# fmt: on


def getMixinKey(orig: str) -> str:
    """
    对 imgKey 和 subKey 进行字符顺序打乱编码，生成混淆密钥

    参数:
        orig: imgKey和subKey拼接后的字符串

    返回:
        经过编码处理后的32位混淆密钥字符串
    """
    # 使用reduce函数和mixinKeyEncTab对原始字符串进行重排，取前32位
    return reduce(lambda s, i: s + orig[i], mixinKeyEncTab, "")[:32]


def encWbi(params: dict[str, any], img_key: str, sub_key: str) -> dict[str, any]:
    """
    为请求参数进行WBI签名，这是B站API的一种安全验证机制

    参数:
        params: 需要签名的请求参数字典
        img_key: 从B站接口获取的图片密钥
        sub_key: 从B站接口获取的子密钥

    返回:
        包含签名信息的新参数字典
    """
    # 生成混淆密钥
    mixin_key = getMixinKey(img_key + sub_key)
    # 获取当前时间戳(秒级)
    curr_time = round(time.time())
    # 添加时间戳参数
    params["wts"] = curr_time
    # 按照key对参数进行排序，这是签名计算的必要步骤
    params = dict(sorted(params.items()))
    # 过滤参数值中的特殊字符"!'()*"，这是B站API的要求
    params = {
        k: "".join(filter(lambda chr: chr not in "!'()*", str(v)))
        for k, v in params.items()
    }
    # 将参数转换为URL查询字符串格式
    query = urllib.parse.urlencode(params)
    # 计算签名值(w_rid)：将查询字符串与混淆密钥拼接后进行MD5哈希
    wbi_sign = md5((query + mixin_key).encode()).hexdigest()
    # 将签名添加到参数中
    params["w_rid"] = wbi_sign
    return params


async def getWbiKeys() -> tuple[str, str]:
    """
    获取最新的img_key和sub_key，这两个密钥用于生成WBI签名
    这两个密钥会定期更新，因此需要动态获取

    返回:
        包含img_key和sub_key的元组
    """
    # 请求头信息，模拟浏览器行为
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
        "Referer": "https://www.bilibili.com/",
    }

    # 创建异步HTTP客户端并发送请求
    async with httpx.AsyncClient(headers=headers) as client:
        # 请求B站导航接口，该接口包含WBI所需的密钥信息
        resp = await client.get("https://api.bilibili.com/x/web-interface/nav")
        resp.raise_for_status()  # 如果响应状态码不是200，抛出异常
        json_content = resp.json()

    # 从响应中提取图片URL
    img_url: str = json_content["data"]["wbi_img"]["img_url"]
    sub_url: str = json_content["data"]["wbi_img"]["sub_url"]

    # 从URL中提取img_key和sub_key（去掉文件名后缀）
    img_key = img_url.rsplit("/", 1)[1].split(".")[0]
    sub_key = sub_url.rsplit("/", 1)[1].split(".")[0]
    return img_key, sub_key


# 全局的请求头，模拟Edge浏览器访问
headers = {
    "accept": "application/json, text/plain, */*",
    "accept-encoding": "gzip, deflate, br, zstd",
    "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
    "origin": "https://www.bilibili.com",
    "priority": "u=1, i",
    "referer": "https://www.bilibili.com/",
    "sec-ch-ua": '"Chromium";v="140", "Not=A?Brand";v="24", "Microsoft Edge";v="140"',
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": '"Windows"',
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-site",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0",
}


async def fetch_zone_ranking(
    rid: int = 0, ranking_type: str = "all", img_key: str = "", sub_key: str = ""
) -> dict | None:
    """
    获取B站分区视频排行榜数据，带WBI签名验证

    参数：
        rid: 分区ID, 默认为0(全站)，例如1=动画区，3=音乐区
        ranking_type: 排行榜类型, 可选值: all(全部), rokkie(新人), origin(原创)
        img_key: WBI签名所需的img_key
        sub_key: WBI签名所需的sub_key

    返回：
        包含排行榜数据的字典, 如果请求失败, 返回None
    """

    # 排行榜API的基础URL
    base_url = "https://api.bilibili.com/x/web-interface/ranking/v2"

    # 构建基础请求参数
    params = {
        "rid": rid,
        "type": ranking_type,
        "web_location": "333.934",  # 表示访问位置，固定值
    }

    # 对参数进行WBI签名处理
    signed_params = encWbi(params, img_key, sub_key)

    # 创建异步HTTP客户端
    async with httpx.AsyncClient(
        headers=headers,
        timeout=None,  # 不设置超时时间
        http2=True,  # 使用HTTP/2协议
        follow_redirects=True,  # 自动跟随重定向
    ) as client:
        try:
            # 随机延迟1-3秒，模拟人类浏览行为，避免被反爬机制识别
            delay = random.uniform(2, 5)
            print(f"等待 {delay:.2f} 秒后请求数据...")
            await asyncio.sleep(delay)

            # 发送GET请求
            response = await client.get(base_url, params=signed_params)
            response.raise_for_status()  # 抛出HTTP错误状态码

            print(f"请求成功，状态码: {response.status_code}")
            return response.json()  # 返回JSON格式的响应数据

        except httpx.HTTPError as e:
            print(f"HTTP 请求错误: {str(e)}")
        except Exception as e:
            print(f"发生错误: {str(e)}")

    return None


def get_region_name(tid: int) -> str:
    """根据分区ID获取分区名称"""
    for region in VIDEO_ZONES:
        if region["tid"] == tid:
            return region["name"]
    return "未知分区"


async def main():
    """主函数，按顺序获取各分区排行榜数据"""
    # 获取WBI密钥
    print("获取WBI密钥中...")
    img_key, sub_key = await getWbiKeys()
    print("WBI密钥获取成功\n")

    # # 按顺序遍历排行榜分区（使用导入的sorted_by_ranking列表）
    # for index, tid in enumerate(sorted_by_ranking, 1):
    #     region_name = get_region_name(tid)
    #     print(
    #         f"===== 正在获取第 {index}/{len(sorted_by_ranking)} 个分区: {region_name} (tid={tid}) ====="
    #     )

    #     # 获取当前分区的排行榜数据
    #     ranking_data = await fetch_zone_ranking(tid, img_key=img_key, sub_key=sub_key)

    #     # 处理并打印结果
    #     if ranking_data:
    #         if ranking_data.get("code") == 0 and "data" in ranking_data:
    #             # 只显示前10个视频
    #             print(f"\n{region_name}分区前10名视频:")
    #             for i, item in enumerate(ranking_data["data"]["list"][:10], 1):
    #                 print(
    #                     f"{i:02d}. {item['title']} (播放量: {item['stat']['view']:,})"
    #                 )
    #         else:
    #             print(
    #                 f"\n获取{region_name}分区数据失败: {ranking_data.get('message', '未知错误')}"
    #             )

    #     print("\n" + "=" * 60 + "\n")
    ranking_data = await fetch_zone_ranking(1005, img_key=img_key, sub_key=sub_key)
    print(ranking_data)


if __name__ == "__main__":
    # 运行异步主函数
    asyncio.run(main())
